package com.hrt.datasender.datacollect.drive;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.List;

import com.btcode.common.MyLog;
import com.btcode.log.ILog;
import com.hrt.datasender.datadao.model.BcDeviceConfigModel;
import com.hrt.datasender.datadao.model.BcDeviceMonitorParamModel;
import com.hrt.datasender.datadao.model.BcDevicesModel;
import com.hrt.datasender.datadao.model.BdSourceModel;

/**
 * 2018年1月22日
 * 
 * 驱动抽象类，做一些通用处理，抽象成和串口或者网口无关
 * 
 * <br>
 * 重构后的串口modbus驱动，把之前同时继承SingleInstanceJob和实现IDrive的方式改掉。 <br>
 * 因为由于quartz机制的原因，SingleInstanceJob的生命周期和IDrive是不同的，比如在实现的时候，虽然写在同一个驱动逻辑里。 <br>
 * 但，调用SingleInstanceJob接口时用到的实例，和调用IDrive接口时用到的实例是不同的，这在后续的逻辑维护上存在隐患。 <br>
 * 故，把SingleInstanceJob和IDrive拆开来实现，通过组合的方式放到一个驱动里。 <br>
 * 
 * <h1>具体逻辑流程</h1>
 * 
 * 1、初始化驱动配置：DriveConfigModel configs和监测项映射：HashMap<Integer,String>
 * paramCodeMap，以及一些必要的设置 <br>
 * 2、调用registerAskDataJob注册询问数据的定时器，定时向设备发送消息，用于实现询问数据的场景，registerAskDataJob方法留给子类实现
 * <br>
 * 3、调用registerMQListener注册消息监听，监听设备传来的数据，registerMQListener方法留给子类实现，
 * 在这个方法的实现中，获取到数据后，要主动调用setSourceData方法，否则后续的流程将失效 <br>
 * 4、实现IDrive的getCurrentData接口，里面做数据深拷贝的处理,向上层框架提供数据
 * 
 * @author gislin
 *
 */
public abstract class DriveBase implements IDrive {

    /**
     * 可供子类调用
     */
    protected ILog log;

    private BcDevicesModel device;

    private DriveConfigModel configs = new DriveConfigModel();

    private List<BdSourceModel> sourceData;

    private HashMap<Integer, String> paramCodeMap = new HashMap<>();

    /**
     * 初始化方法，
     */
    @Override
    public void init(BcDevicesModel device, List<BcDeviceMonitorParamModel> params, List<BcDeviceConfigModel> configs) {
        try {
            log = MyLog.getInstance().getLogger(getClass().getName() + "." + device.getDeviceName());

            log.info("获取设备参数信息：" + params);
            log.info("获取设备信息：" + device);

            beforeInit(device, params, configs);

            this.device = device;

            HashMap<String, String> configMap = new HashMap<>();

            /**
             * 把参数列表转换成map，方便报文解析转换处理
             */
            for (BcDeviceMonitorParamModel parm : params) {
                int codePro = Integer.parseInt(parm.getParamCodePro());
                paramCodeMap.put(codePro, parm.getParamCodeBus());
            }

            /**
             * 把配置列表转换成map，方便处理
             */
            for (BcDeviceConfigModel parm : configs) {
                configMap.put(parm.getName(), parm.getValue());
            }

            this.configs.setConfig(configMap);
            this.configs.setName(this.device.getDeviceName());
            this.configs.put("name", this.device.getDeviceName());

            String slaveNo = configMap.get("slaveNo") + "";
            /**
             * 这一步重要，否则后面消费串口消息的时候，会取不到数据
             */
            this.device.setDeviceNum(slaveNo);

            registerMQListener(device, paramCodeMap, this.configs);
            /**
             * 注册数据询问定时器
             */
            registerAskDataJob(device, this.configs);

            log.info("设备初始化完成：" + device.getDeviceName());
        }
        catch (Exception e) {
            log.error("初始化驱动出错", e);
        }
    }

    /**
     * 获取当前，驱动已经处理好的数据 和getSourceData的区别是，这个方法对数据进行了深度拷贝，并加了同步锁
     * 避免在后续业务逻辑中，驱动持续修改数据, 暴露给上层业务获取
     * 
     * @return
     */
    public List<BdSourceModel> getCurrentData() {

        List<BdSourceModel> dataGet = getSourceData();

        if (dataGet == null) {
            return null;
        }

        synchronized (DriveBase.class) {
            try {
                List<BdSourceModel> returnResult = deepCopy(dataGet);
                /**
                 * 置空，下次循环如果没有新数据就会自动忽略
                 */
                setSourceData(null);
                return returnResult;
            }
            catch (ClassNotFoundException | IOException e) {
                log.error("拷贝数据出错", e);
            }
        }

        return null;

    }

    private List<BdSourceModel> getSourceData() {
        return sourceData;
    }

    protected void setSourceData(List<BdSourceModel> sourceData) {
        this.sourceData = sourceData;
    }

    /**
     * 深拷贝数据
     * 
     * @param src
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(src);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        @SuppressWarnings("unchecked")
        List<T> dest = (List<T>) in.readObject();
        return dest;
    }

    public String toString() {
        StringBuffer strBuf = new StringBuffer("driveClass:" + this.getClass().getName() + ",");

        if (device != null) {
            strBuf.append("COMNum:" + device.getComNum());
        }

        return strBuf.toString();
    }

    protected abstract void beforeInit(BcDevicesModel device, List<BcDeviceMonitorParamModel> params,
            List<BcDeviceConfigModel> configs);

    protected abstract void registerMQListener(BcDevicesModel device, HashMap<Integer, String> paramCodeMap,
            DriveConfigModel configs);

    protected abstract void registerAskDataJob(BcDevicesModel device, DriveConfigModel configs);

}
